package info.cs52.fes.prolog;

import info.cs52.fes.action.QuestionTypeBundle;
import info.cs52.fes.lp.PrologLanguageParser;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javax.persistence.criteria.CriteriaBuilder.In;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;

import alice.tuprolog.InvalidLibraryException;
import alice.tuprolog.InvalidTheoryException;
import alice.tuprolog.Prolog;
import alice.tuprolog.SolveInfo;
import alice.tuprolog.Struct;
import alice.tuprolog.Term;
import alice.tuprolog.Theory;
import alice.tuprolog.event.WarningEvent;
import alice.tuprolog.event.WarningListener;

public class TuPrologEngine implements Engine {

	private Prolog engine;
	private KnowledgeLibrary userKb;
	private FesLibrary fesLib = new FesLibrary();
	private int waitingAnswerType = PrologLanguageParser.STRING;
	
	public int getWaitingAnswerType() {
		return waitingAnswerType;
	}

	public void setWaitingAnswerType(int waitingAnswerType) {
		this.waitingAnswerType = waitingAnswerType;
	}

	public TuPrologEngine() {
		try {
			long start = System.currentTimeMillis();
			engine = new Prolog();
			engine.addWarningListener(new WarningListener() {
				
				@Override
				public void onWarning(WarningEvent e) {
					System.err.println(e.getMsg());
				}
			});
			engine.loadLibrary(fesLib);
			engine.addTheory(new Theory(readAllTheory()));
			long end = System.currentTimeMillis();
			System.out.println("Init time: " + (end-start)/1000.0 + "s");
//			initDynamicTheory();
		} catch (Exception e) {
			throw new PrologException(e);
		}
	}

//	private void initDynamicTheory() throws InvalidTheoryException {
//		GregorianCalendar cal = new GregorianCalendar();
//		String theoryStr = "year(" + cal.get(Calendar.YEAR) + ").\n" +
//				"month(" + (cal.get(Calendar.MONTH)+1) + ").\n" +
//				"date(" + cal.get(Calendar.DATE) + ").\n" +
//				"day_of_week(" + cal.get(Calendar.DAY_OF_WEEK) + ").\n" +
//				"hour(" + cal.get(Calendar.HOUR) + ").\n" +
//				"minute(" + cal.get(Calendar.MINUTE) + ").";
//		engine.addTheory(new Theory(theoryStr));
//	}
	
	@Override
	public String getUserKnowledgeBase() {
		return userKb == null ? "" : userKb.getKnowledge();
	}
	
	@Override
	public void setUserKnowledgeBase(String s) {
		try {
			engine.addTheory(new Theory(s));
			userKb = new KnowledgeLibrary(s);
			engine.loadLibrary(userKb);
		} catch (InvalidLibraryException e) {
			throw new PrologException(e);
		} catch (InvalidTheoryException e) {
			throw new PrologException(e);
		}
	}
	
	public List<List<String>> recommend() {
		try {
			long start = System.currentTimeMillis();
			List<List<String>> meals = new ArrayList<List<String>>();
			
			SolveInfo info = engine.solve("meals(X, CF).");
			while (info.isSuccess()) {
				List<String> meal = new ArrayList<String>();

				Term x = info.getVarValue("X");
				if (x instanceof Struct) {
					Struct term = (Struct) x;
					if (term.isEmptyList()) {
						continue;
					}
					Struct head = (Struct) term.getArg(0).getTerm();
					Struct tail = (Struct) term.getArg(1);
					meal.add(head.getName());
					
					while (!tail.isEmptyList()) {
						head = (Struct) tail.getArg(0).getTerm();
						tail = (Struct) tail.getArg(1);
						meal.add(head.getName());
					}

					meals.add(meal);
				}
				if (engine.isHalted()) {
					System.err.println("Halted!!!");
				}
				
				if (engine.hasOpenAlternatives()) {
					info = engine.solveNext();
				} else {
					break;
				}
			}
			
			long end = System.currentTimeMillis();
			System.out.println("Recommend time: " + (end-start)/1000.0 + "s");
			
			getNextAnswerType();
			
			return meals;
		} catch (Exception e) {
			throw new PrologException(e);
		}
	}

	@Override
	public String process(String question, String answer, float cf) {
		try {
			String term = "fact(av(" + question + ", " + answer + "), " + cf + ").";
			engine.addTheory(new Theory(term));
			userKb.addKnowledge(term);
			return term;
		} catch (InvalidTheoryException e) {
			throw new PrologException(e);
		}
	}

	@Override
	public String getQuestion() {
		return userKb.getQuestion();
	}
	
	@Override
	public String getPrompt() {
		return userKb.getPrompt();
	}
	
	public Prolog getTuProlog() {
		return engine;
	}

	private static InputStream openStream(String path) {
		return new BOMInputStream(
				TuPrologEngine.class.getResourceAsStream(path));
	}
	
	private String readAllTheory() throws IOException {
		StringWriter sw = new StringWriter();
		try {
			IOUtils.copy(openStream("/prolog/deduction.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge_age.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge_health.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge_job.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge_like_datetime_special.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/knowledge_weather.pl"), sw, "UTF-8");
			sw.write("\n");
			IOUtils.copy(openStream("/prolog/recipes.pl"), sw, "UTF-8");
			return sw.toString();
		} finally {
			sw.close();
		}
	}
	
	private void getNextAnswerType(){
		String question= userKb.getQuestion();
		ResourceBundle bundle = QuestionTypeBundle.getBundle();
		try{
			waitingAnswerType = Integer.parseInt(bundle.getString(question));
		}catch (Exception e) {
			// TODO: handle exception
		}
	}
	
}
